using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace VideoSource
{

    public class WebCamera
    {

        //private const int CAPTURE_WIDTH = 640;                                                           

        //private const int CAPTURE_HEIGHT = 480;

        private int captureWidth;
        private int captureHeight;

        private Form innerForm = new Form();



        // delegate for frame callback

        public event EventHandler<WebCameraEventArgs> RecievedFrame;

        private IntPtr lwndC;

        private int mWidth;

        private int mHeight;

        private API32.FrameEventHandler mFrameEventHandler; // Delegate instance for the frame callback - must keep alive! gc should NOT collect it



        // Constructur

        public WebCamera(int width, int height)
        {

            innerForm.Opacity = 0;

            innerForm.TopMost = true;

            innerForm.ShowInTaskbar = false;



            mWidth = width;

            mHeight = height;

        }





        // Close the web camera

        public void CloseWebcam()
        {

            this.capDriverDisconnect(this.lwndC);

        }



        // start the web camera

        public void StartWebCam()
        {

            innerForm.Show();

            byte[] lpszName = new byte[100];

            byte[] lpszVer = new byte[100];

            API32.capGetDriverDescriptionA(0, lpszName, 100, lpszVer, 100);

            this.lwndC = API32.capCreateCaptureWindowA(lpszName, API32.WS_VISIBLE + API32.WS_CHILD, 0, 0, mWidth, mHeight, innerForm.Handle, 0);



            if (this.capDriverConnect(this.lwndC, 0))
            {

                this.capPreviewRate(this.lwndC, 66);

                this.capPreview(this.lwndC, true);

                API32.BITMAPINFO bitmapinfo = new API32.BITMAPINFO();

                bitmapinfo.bmiHeader.biSize = API32.SizeOf(bitmapinfo.bmiHeader);

                capGetVideoFormat(this.lwndC, ref bitmapinfo, API32.SizeOf(bitmapinfo));

                //bitmapinfo.bmiHeader.biWidth = CAPTURE_WIDTH;

                //bitmapinfo.bmiHeader.biHeight = CAPTURE_HEIGHT;

                captureWidth = bitmapinfo.bmiHeader.biWidth;
                captureHeight = bitmapinfo.bmiHeader.biHeight;

                //bitmapinfo.bmiHeader.biPlanes = 1;

                //bitmapinfo.bmiHeader.biBitCount = 24;

                this.capSetVideoFormat(this.lwndC, ref bitmapinfo, API32.SizeOf(bitmapinfo));

                this.mFrameEventHandler = new API32.FrameEventHandler(FrameCallBack);

                this.capSetCallbackOnFrame(this.lwndC, this.mFrameEventHandler);

                API32.SetWindowPos(this.lwndC, 0, 0, 0, mWidth, mHeight, 6);

            }

        }

        public void ShowConfigDialog()
        {
            CloseWebcam();
            byte[] lpszName = new byte[100];
            byte[] lpszVer = new byte[100];

            API32.capGetDriverDescriptionA(0, lpszName, 100, lpszVer, 100);
            this.lwndC = API32.capCreateCaptureWindowA(lpszName, API32.WS_VISIBLE + API32.WS_CHILD, 0, 0, mWidth, mHeight, innerForm.Handle, 0);

            if (capDriverConnect(this.lwndC, 0))
            {
                capDlgVideoFormat(this.lwndC);
                CloseWebcam();
            }
            else
                throw new Exception("Camera not found");
        }


        // private functions

        private bool capDriverConnect(IntPtr lwnd, short i)
        {

            return API32.SendMessage(lwnd, API32.WM_CAP_DRIVER_CONNECT, i, 0);

        }



        private bool capDriverDisconnect(IntPtr lwnd)
        {

            return API32.SendMessage(lwnd, API32.WM_CAP_DRIVER_DISCONNECT, 0, 0);

        }



        private bool capPreview(IntPtr lwnd, bool f)
        {

            return API32.SendMessage(lwnd, API32.WM_CAP_SET_PREVIEW, f, 0);

        }



        private bool capPreviewRate(IntPtr lwnd, short wMS)
        {

            return API32.SendMessage(lwnd, API32.WM_CAP_SET_PREVIEWRATE, wMS, 0);

        }



        private bool capSetCallbackOnFrame(IntPtr lwnd, API32.FrameEventHandler lpProc)
        {

            return API32.SendMessage(lwnd, API32.WM_CAP_SET_CALLBACK_FRAME, 0, lpProc);

        }



        private bool capSetVideoFormat(IntPtr hCapWnd, ref API32.BITMAPINFO BmpFormat, int CapFormatSize)
        {

            return API32.SendMessage(hCapWnd, API32.WM_CAP_SET_VIDEOFORMAT, CapFormatSize, ref BmpFormat);

        }

        private bool capDlgVideoFormat(IntPtr lwnd)
        {
            return API32.SendMessage(lwnd, API32.WM_CAP_DLG_VIDEOFORMAT, 0, 0);
        }

        private volatile bool capturing = false;

        private void FrameCallBack(IntPtr lwnd, IntPtr lpVHdr)
        {
            if (capturing)
                return;
            capturing = true;

            API32.VIDEOHDR videoHeader = new API32.VIDEOHDR();

            byte[] data;

            videoHeader = (API32.VIDEOHDR)API32.GetStructure(lpVHdr, videoHeader);

            data = new byte[videoHeader.dwBytesUsed];

            API32.Copy(videoHeader.lpData, data);

            System.Drawing.Bitmap bmp = (Bitmap)new Bitmap(captureWidth, captureHeight);

            System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, captureWidth, captureHeight), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            //unsafe
            //{

            //    fixed (byte* b = data)
            //    {
            //        IntPtr ptr = new IntPtr(b);
            //        bmpData.Scan0 = ptr;

            //    }

            //}

            Marshal.Copy(data, 0, bmpData.Scan0, data.Length);

            bmp.UnlockBits(bmpData);

            bmp.RotateFlip(RotateFlipType.Rotate180FlipX);

            OnRecievedFrame(new WebCameraEventArgs(data, bmp));

            capturing = false;

        }

        private void OnRecievedFrame(WebCameraEventArgs e)
        {

            if (this.RecievedFrame != null)
            {

                this.RecievedFrame(this, e);

            }

        }

        #region extended

        public void ShowVideoSource()
        {
            if (lwndC == IntPtr.Zero)
            {
                byte[] lpszName = new byte[100];
                byte[] lpszVer = new byte[100];

                API32.capGetDriverDescriptionA(0, lpszName, 100, lpszVer, 100);
                this.lwndC = API32.capCreateCaptureWindowA(lpszName, API32.WS_VISIBLE + API32.WS_CHILD, 0, 0, mWidth, mHeight, innerForm.Handle, 0);

                if (capDriverConnect(this.lwndC, 0))
                {
                    capDlgVideoSource(lwndC);
                }
            }
            else
            {
                capDlgVideoSource(lwndC);
            }

        }

        public Size GetFrameSize()
        {
            API32.BITMAPINFO bitmapinfo = new API32.BITMAPINFO();

            bitmapinfo.bmiHeader.biSize = API32.SizeOf(bitmapinfo.bmiHeader);

            capGetVideoFormat(this.lwndC, ref bitmapinfo, API32.SizeOf(bitmapinfo));

            return new Size(bitmapinfo.bmiHeader.biWidth, bitmapinfo.bmiHeader.biHeight);

        }

        public bool SetFrameSize(Size size)
        {
            API32.BITMAPINFO bitmapinfo = new API32.BITMAPINFO();

            bitmapinfo.bmiHeader.biSize = API32.SizeOf(bitmapinfo.bmiHeader);

            capGetVideoFormat(this.lwndC, ref bitmapinfo, API32.SizeOf(bitmapinfo));

            bitmapinfo.bmiHeader.biWidth = size.Width;
            bitmapinfo.bmiHeader.biHeight = size.Height;
            captureWidth = bitmapinfo.bmiHeader.biWidth;
            captureHeight = bitmapinfo.bmiHeader.biHeight;

            return this.capSetVideoFormat(this.lwndC, ref bitmapinfo, API32.SizeOf(bitmapinfo));

        }

        public void GetCaptureParams()
        {
            API32.CAPTUREPARMS cParams = new API32.CAPTUREPARMS();
            CloseWebcam();
            byte[] lpszName = new byte[100];
            byte[] lpszVer = new byte[100];

            API32.capGetDriverDescriptionA(0, lpszName, 100, lpszVer, 100);
            this.lwndC = API32.capCreateCaptureWindowA(lpszName, API32.WS_VISIBLE + API32.WS_CHILD, 0, 0, mWidth, mHeight, innerForm.Handle, 0);

            if (capDriverConnect(this.lwndC, 0))
            {
                capCaptureGetSetup(lwndC, ref cParams, API32.SizeOf(cParams));
                CloseWebcam();
            }
        }

        public bool SetFramesPerSec(double fps)
        {
            API32.CAPTUREPARMS cParams = new API32.CAPTUREPARMS();
            capCaptureGetSetup(lwndC, ref cParams, API32.SizeOf(cParams));
            cParams.dwRequestMicroSecPerFrame = Convert.ToUInt32((1.0 / fps) * 1000000.0);
            cParams.wStepCaptureAverageFrames = 5;
            bool ret = capCaptureSetSetup(lwndC, ref cParams, API32.SizeOf(cParams));
            return ret;
        }

        public double GetFramesPerSec()
        {
            API32.CAPTUREPARMS cParams = new API32.CAPTUREPARMS();
            capCaptureGetSetup(lwndC, ref cParams, API32.SizeOf(cParams));
            return 1.0 / (cParams.dwRequestMicroSecPerFrame / 1000000.0);
        }

        private bool capDlgVideoSource(IntPtr hWnd)
        {
            return API32.SendMessage(hWnd, API32.WM_CAP_DLG_VIDEOSOURCE, 0, 0);
        }

        private bool capGetVideoFormat(IntPtr hCapWnd, ref API32.BITMAPINFO BmpFormat, int CapFormatSize)
        {

            return API32.SendMessage(hCapWnd, API32.WM_CAP_GET_VIDEOFORMAT, CapFormatSize, ref BmpFormat);

        }

        private bool capCaptureGetSetup(IntPtr hCapWnd, ref API32.CAPTUREPARMS captureParams, int wSize)
        {
            return API32.SendMessage(hCapWnd, API32.WM_CAP_GET_SEQUENCE_SETUP, wSize, ref captureParams);
        }

        private bool capCaptureSetSetup(IntPtr hCapWnd, ref API32.CAPTUREPARMS captureParams, int wSize)
        {
            return API32.SendMessage(hCapWnd, API32.WM_CAP_SET_SEQUENCE_SETUP, wSize, ref captureParams);
        }
        #endregion


    }

    public class WebCameraEventArgs : EventArgs
    {


        private readonly System.Drawing.Bitmap bitMap;

        private readonly byte[] data;



        public WebCameraEventArgs(byte[] videoData, System.Drawing.Bitmap bmp)
        {

            data = videoData;

            bitMap = bmp;

        }



        public byte[] Data
        {

            get { return data; }

        }



        public System.Drawing.Bitmap Bitmap
        {

            get { return bitMap; }

        }

    }

}
